#include "longinstance.h"
#include "freefuncs.h"
#include <string>
#include <iostream>
#include <math.h>
#include <cassert>
using namespace std;


void LongInstance::addPhase (const Neighborhood& newNB, int newLabel)
{
	Neighborhood* localNew = new Neighborhood(newNB);
	this->neighborhood.push_back(localNew);
	this->updateTotalHash();
	this->label = newLabel;
}

void LongInstance::addPhase(const ImageMatrix& image, int row, int col, int newLabel)
{
	Neighborhood* localNew = new Neighborhood(image, row, col);
	this->neighborhood.push_back(localNew);
	this->label = newLabel;
	this->updateTotalHash();
}

void LongInstance::addOthersCoordinates(const LongInstance& other)
{
	for (int i = 0; i < (int) other.coordinate.size(); i++)
		this->addCoordinate(other.coordinate[i]);
}

int LongInstance::getFeature (int neighborhoodIndex, int featureIndex) const
{
	assert(neighborhoodIndex < (int) this->neighborhood.size() && featureIndex < intPosPow((2*NRADIUS+1),2));
	return this->neighborhood[neighborhoodIndex]->getFeature(featureIndex);
}

int LongInstance::getFeature (int longFeatureIndex) const
{
	assert(longFeatureIndex < (int) this->neighborhood.size() * intPosPow((2*NRADIUS+1),2) &&
		"assertion failed: trying to read an out of range feature");
	int neighborhoodIndex = longFeatureIndex / intPosPow((2*NRADIUS+1),2);
	int featureIndex = longFeatureIndex % intPosPow((2*NRADIUS+1),2);
	return this->neighborhood[neighborhoodIndex]->getFeature(featureIndex);
}

int LongInstance::getOccuranceNum(void) const
{
	return this->coordinate.size();
}

unsigned long LongInstance::getTotalHash () const
{
	return this->totalHash;
}

void LongInstance::setFeature (int neighborhoodIndex, int featureIndex, int value)
{
	assert(neighborhoodIndex < (int) this->neighborhood.size() && featureIndex < intPosPow((2*NRADIUS+1),2)
		&& value < CELLSTATES);
	this->neighborhood[neighborhoodIndex]->setFeature(featureIndex, value); //updates the internal hash too
	this->updateTotalHash();
}

void LongInstance::setFeature (int longFeatureIndex, int value)
{
	assert(longFeatureIndex < (int) this->neighborhood.size() * intPosPow((2*NRADIUS+1),2) &&
		value < CELLSTATES);
	int neighborhoodIndex = longFeatureIndex / intPosPow((2*NRADIUS+1),2);
	int featureIndex = longFeatureIndex % intPosPow((2*NRADIUS+1),2);
	this->neighborhood[neighborhoodIndex]->setFeature(featureIndex, value);
	this->updateTotalHash();
}

void LongInstance::setLabel (int value)
{
	assert (label < CELLSTATES);
	this->label = value;
}

LongInstance & LongInstance::operator= (const LongInstance& other)
{
	if (&other == this)
		return *this;
	vector<Neighborhood*>::const_iterator i = this->neighborhood.begin();
	for (; i != neighborhood.end(); ++i)
		delete *i;
	this->neighborhood.clear();
	for (int i = 0; i < (int) other.neighborhood.size(); i++)
	{
		Neighborhood* localNB = new Neighborhood(*other.neighborhood[i]);
		this->neighborhood.push_back(localNB);
	}
	this->coordinate.clear();
	for (int i = 0; i != (int) other.coordinate.size(); i++)
		this->coordinate.push_back(Coordinate(other.coordinate[i]));
	this->totalHash = other.totalHash;
	this->conflictNum = other.conflictNum;
	this->label = other.label;
	return * this;
}
		
bool LongInstance::hasSameFeature (const LongInstance& other) const
{
	if (this->neighborhood.size() != other.neighborhood.size())
		return false;
	for (int i = 0; i != (int) this->neighborhood.size(); i++)
		if (!this->neighborhood[i]->hasSameFeature(*other.neighborhood[i]))
			return false;
	return true;
}

bool LongInstance::hasSameFeature(const vector<Neighborhood*>& nbr) const
{
	if (this->neighborhood.size() != nbr.size())
		return false;
	else
		for (int i = 0; i != (int)nbr.size(); i++)
			if (!this->neighborhood[i]->hasSameFeature(*nbr[i]))
				return false;
	return true;
}

bool LongInstance::hasSamePartialHash(LongInstance& other) const
{
	if (this->neighborhood.size() != other.neighborhood.size())
		return false;
	for (int i = 0; i != (int) this->neighborhood.size(); i++)
		if (this->neighborhood[i]->getHash() != other.neighborhood[i]->getHash()) //TODO: This hash thing is not a good idea. Let's remove it.
			return false;
	return true;
}
	
void LongInstance::print(void) const
{
	for (int i = 0; i != (int) this->neighborhood.size(); i++)
	{
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			cout << this->neighborhood[i]->getFeature(j) << "";
		cout << ", ";
	}
	cout << ": " << this->label << endl;
}

void LongInstance::printDetail(void) const
{
	cout << "LongInstance features: " << endl;
	for (int i = 0; i != (int)this->neighborhood.size(); i++)
	{
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			cout << this->neighborhood[i]->getFeature(j) << " ";
		cout << ", ";
	}
	cout << "- " << this->label << endl;
	cout << "LongInstance hashes: " << endl;
	for (int i = 0; i != (int)this->neighborhood.size(); i++)
		cout << this->neighborhood[i]->getHash() << ", ";
	cout << endl;
	cout << "Total hash: " << this->totalHash << endl;
	cout << "Number of coordinates where this instance occurs: " << this->coordinate.size() << endl;
	cout << "Coordinate where this instance occurs: " << endl;
	for (int i = 0; i != (int)this->coordinate.size(); i++)
	{
		this->coordinate[i].print();
		cout << " ";
	}
	cout << endl;
}

ostream& operator<< (ostream& stream, const LongInstance& li)
{
	for (int i = 0; i != (int)li.neighborhood.size(); i++)
	{
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			stream << li.neighborhood[i]->getFeature(j) << " ";
		stream << ", ";
	}
	stream << "-> " << li.label << endl;
	return stream;
}

bool LongInstance::isNextPhase (LongInstance& prevPhase) const
{
	if (this->neighborhood.size() != prevPhase.neighborhood.size() + 1)
		return false;
	for (int i = 0; i != (int)prevPhase.neighborhood.size(); i++)
		if (!this->neighborhood[i]->hasSameFeature(*prevPhase.neighborhood[i]))
			return false;
	return true;
}

bool LongInstance::isPrevPhase (LongInstance& nextPhase) const
{
	if (this->neighborhood.size() != nextPhase.neighborhood.size() - 1)
		return false;
	for (int i = 0; i != (int) this->neighborhood.size(); i++)
		if (!this->neighborhood[i]->hasSameFeature(*nextPhase.neighborhood[i]))
			return false;
	return true;
}

void LongInstance::updateTotalHash(void)
{
	int bitOrder = 0;
	for (int i = 0; i != (int)this->neighborhood.size(); i++)
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
		{
			this->totalHash += this->neighborhood[i]->getFeature(j) * intPosPow(CELLSTATES, bitOrder);
			bitOrder++;
		}
}

void LongInstance::updateFromPartialHashArray(unsigned long* hashValue, int hashValueSize, int label)
{
	vector<Neighborhood*>::const_iterator i = this->neighborhood.begin();
	for (; i != neighborhood.end(); ++i)
		delete *i;
	this->neighborhood.clear();
	for (int i = 0; i != hashValueSize; i++)
	{
		Neighborhood* toBeAdded = new Neighborhood(hashValue[i]);
		this->neighborhood.push_back(toBeAdded);
	}
	this->label = label;
	this->updateTotalHash();
}

void LongInstance::clear()
{
	vector<Neighborhood*>::const_iterator i = this->neighborhood.begin();
	for (; i != neighborhood.end(); ++i)
		delete *i;
	this->neighborhood.clear();
	this->coordinate.clear();
	this->label = -1;
	this->totalHash = -1;
	this->conflictNum = 0;
}
	
void LongInstance::getCooBorders(int& top, int& bottom, int& left, int& right) const
{
	int cooSize = this->getOccuranceNum();
	assert (cooSize > 0);

	int t = this->getRowOfCoordinate(0);
	int b = t;
	int l = this->getColOfCoordinate(0);
	int r = l;
	
	if (cooSize > 1)
		for (int i = 1; i < cooSize; i++)
		{
			if (t > this->getRowOfCoordinate(i))
				t = this->getRowOfCoordinate(i);
			else if (b < this->getRowOfCoordinate(i))
				b = this->getRowOfCoordinate(i);
			if (l > this->getColOfCoordinate(i))
				l = this->getColOfCoordinate(i);
			else if ( r < this->getColOfCoordinate(i))
				r = this->getColOfCoordinate(i);
		}

		top = t;
		bottom = b;
		left = l;
		right = r;
}

LongInstance::LongInstance ()
{
	//this->label = -1;
	//this->totalHash = -1;
	//this->conflictNum = 0;
}

LongInstance::LongInstance(const LongInstance& other)
{
	this->label = other.label;
	this->totalHash = other.totalHash;
	this->conflictNum = other.conflictNum;
	for (int i = 0; i != (int)other.coordinate.size(); i++)
		this->coordinate.push_back(Coordinate(other.coordinate[i]));
	for (int i = 0; i != (int)other.neighborhood.size(); i++)
		this->neighborhood.push_back(new Neighborhood(*other.neighborhood[i]));
}

LongInstance::LongInstance (string feature,  int label)
{
	assert(feature.size() % intPosPow((2*NRADIUS+1),2) == 0);
	int a = 0;
	int neighborhoodNum = feature.size() / intPosPow((2*NRADIUS+1),2);
	Neighborhood* blankNB;
	for (int i = 0; i != neighborhoodNum; i++)
	{
		blankNB = new Neighborhood;
		this->neighborhood.push_back(blankNB);
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			this->neighborhood[i]->setFeatureLeaveHash(j, feature.at(a++) - 48); //'0'=48, '1'=49, etc.
		this->neighborhood[i]->updateHash();
	}
	this->label = label;
	this->conflictNum = 0;
	this->updateTotalHash();
}
	
	
LongInstance::LongInstance (int* feature, int featureSize, int label)
{
	assert(featureSize % intPosPow((2*NRADIUS+1),2) == 0);
	int a = 0;
	int neighborhoodNum = featureSize / intPosPow((2*NRADIUS+1),2);
	Neighborhood* blankNB;
	for (int i = 0; i != neighborhoodNum; i++)
	{
		blankNB = new Neighborhood;
		this->neighborhood.push_back(blankNB);
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			this->neighborhood[i]->setFeatureLeaveHash(j, feature[a++]);
		this->neighborhood[i]->updateHash();
	}
	this->label = label;
	this->conflictNum = 0;
	this->updateTotalHash();
}
	
LongInstance::LongInstance (string feature, int label, int row, int col)
{
	assert(feature.size() % intPosPow((2*NRADIUS+1),2) == 0);
	int a = 0;
	int neighborhoodNum = feature.size() / intPosPow((2*NRADIUS+1),2);
	Neighborhood* blankNB;
	for (int i = 0; i != neighborhoodNum; i++)
	{
		blankNB = new Neighborhood;
		this->neighborhood.push_back(blankNB);
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			this->neighborhood[i]->setFeatureLeaveHash(j, feature.at(a++) - 48); //'0'=48, '1'=49, etc.
		this->neighborhood[i]->updateHash();
	}
	this->coordinate.push_back(Coordinate(row, col));
	this->label = label;
	this->conflictNum = 0;
	this->updateTotalHash();
}
	
	
LongInstance::LongInstance (int* feature, int featureSize, int label, int row, int col)
{
	assert(featureSize % intPosPow((2*NRADIUS+1),2) == 0);
	int a = 0;
	int neighborhoodNum = featureSize / intPosPow((2*NRADIUS+1),2);
	Neighborhood* blankNB;
	for (int i = 0; i != neighborhoodNum; i++)
	{
		blankNB = new Neighborhood;
		this->neighborhood.push_back(blankNB);
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
			this->neighborhood[i]->setFeatureLeaveHash(j, feature[a++]);
		this->neighborhood[i]->updateHash();
	}
	this->coordinate.push_back(Coordinate(row, col));
	this->label = label;
	this->conflictNum = 0;
	this->updateTotalHash();
}

//Warning: LongInstance has no idea about EightLayerIM
//LongInstance::LongInstance (const EightLayerIM& images, int row, int col, int size): conflictNum(0)
//{
//	this->coordinate.push_back(Coordinate(row, col));
//	for (int i = 0; i < images.getValidImages()-1; i++)
//		this->neighborhood.push_back(Neighborhood(images.getImage(i), row, col));
//	this->label = images.getImage(images.getValidImages()-1).getValue(row, col);
//}


LongInstance::~LongInstance()
{
	vector<Neighborhood*>::iterator i = this->neighborhood.begin();
	for (; i != neighborhood.end(); ++i)
		delete *i;
	this->neighborhood.clear();
	this->coordinate.clear();
	this->label = 0;
	this->totalHash = 0;
	this->conflictNum = 0;

}

int LongInstance::getFeatureSize() const
{
	if (this->neighborhood.size() == 0)
		return 0;
	return (int)this->neighborhood.size() * this->neighborhood[0]->getSize();
}

int LongInstance::getNetInfoSizeInBits(void) const
{
	/*
	  The effective information in an object:
	  one if-then rule and n rectangles (minor rectangles).
	  the rule size is "getFeatureSize()" number of bits for the if part and one bit for the "then" part.
	  the rectangles need n * 2 (2 coordinate is stored per rectangle) * 2*8 (number of bits per coo) bits.
	  */
	//throw(exception("not implemented"));
	return (this->getFeatureSize() + 1 + this->coordinate.size() *2*BITS_PER_ROWCOL);
}

int LongInstance::getNeighborhoodSize() const
{
	assert (this->neighborhood.size() > 0 && "There is no neighborhood to return its size.");
	return neighborhood[0]->getSize();
}

vector<Coordinate>& LongInstance::getCoordinateVector()
{
	return coordinate;
}